﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using WMM.Tapioca.Instance;
using WMM.Tapioca.Taxonomy;
using System.Text.RegularExpressions;
using WMM.Tapioca.Linkbase;
using WMM.Tapioca.Utilities;

namespace WMM.Tapioca.Formula.Filters
{
    /// <summary>
    /// The concept relation filter matches facts based upon the effective relationships of their concepts 
    /// to the source concept, in a specified linkroleURI network of effective relationships, of a 
    /// specified arcroleURI, on a specified axis, inclusive of specified generations, and meeting
    /// an optional test expression.
    /// </summary>
    public class ConceptRelationFilter : Filter
    {
        private string test;

        /// <returns> The XPath expression implied by a general filter. </returns>
        public string Test
        {
            get { return test; }
            set { test = value; }
        }

        private string source;

        /// <returns> The source of the relationships is expressed by term in one of three 
        /// forms(qname, qnameExpression or variable). </returns>
        public string Source
        {
            get { return source; }
            set { source = value; }
        }

        private string sourceElement;

        public string SourceElement
        {
            get { return sourceElement; }
            set { sourceElement = value; }
        }

        private string linkrole;

        /// <returns> The Linkrole is a URI value that specifies the network of effective relationships 
        /// to determine the selected fact concepts on the specified axis from the #source concept used as the origin. </returns>
        public string Linkrole
        {
            get { return linkrole; }
            set { linkrole = value; }
        }

        private string arcrole;

        /// <returns> The Arcrole is a URI value that specifies the network of effective relationships 
        /// to determine the selected fact concepts on the specified axis from the #source concept used as the origin. </returns>
        public string Arcrole
        {
            get { return arcrole; }
            set { arcrole = value; }
        }

        private string linkname;

        /// <returns> The Linkname is an optional QName value that specifies the link element name when needed to unambiguously
        /// determine the base set network of effective relationships to determine the selected fact concepts on the specified 
        /// axis from the #source concept used as the origin. </returns>
        public string Linkname
        {
            get { return linkname; }
            set { linkname = value; }
        }

        private string arcname;

        /// <returns> The Arcname is an optional QName value that specifies the link element name when needed to unambiguously
        /// determine the base set network of effective relationships to determine the selected fact concepts on the specified 
        /// axis from the #source concept used as the origin. </returns>
        public string Arcname
        {
            get { return arcname; }
            set { arcname = value; }
        }

        private string axis;

        /// <returns> The Axis value. </returns>
        public string Axis
        {
            get { return axis; }
            set { axis = value; }
        }

        private int generations;

        /// <returns> The XPath expression implied by a general filter. </returns>
        public int Generations
        {
            get { return generations; }
            set { generations = value; }
        }

        private HashSet<Arc> networkRelationship;

        /// <summary>
        /// Constructor.
        /// </summary>
        public ConceptRelationFilter(Linkbase.Resource resource)
            : base(resource)
        {
            string expressionTest = (string)resource.ResourceXElement.Attribute("test") ?? string.Empty;
            expressionTest = expressionTest.TrimEnd().TrimStart();
            expressionTest = Regex.Replace(expressionTest, @"\s+", " ");
            test = expressionTest;

            // source concept

            XElement variable = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("variable")).FirstOrDefault();

            if (variable != null)
            {
                source = variable.Value;
                sourceElement = "variable";
            }

            XElement name = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("qname")).FirstOrDefault();

            if (name != null)
            {
                source = name.Value;
                sourceElement = "qname";
            }

            XElement expression = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("qnameExpression")).FirstOrDefault();

            if (expression != null)
            {
                source = EvaluateExpression(expression.Value);
                sourceElement = "qnameExpression";
            }

            // Linkrole

            XElement linkroleX = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("linkrole")).FirstOrDefault();

            if (linkroleX != null)
            {
                linkrole = linkroleX.Value;
            }

            XElement linkroleExpression = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("linkroleExpression")).FirstOrDefault();

            if (linkroleExpression != null)
            {
                linkrole = EvaluateExpression(linkroleExpression.Value);
            }

            // arcrole

            XElement arcroleX = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("arcrole")).FirstOrDefault();

            if (arcroleX != null)
            {
                arcrole = arcroleX.Value;
            }

            XElement arcroleExpression = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("arcroleExpression")).FirstOrDefault();

            if (arcroleExpression != null)
            {
                arcrole = EvaluateExpression(arcroleExpression.Value);
            }

            // arcname

            XElement arcnameX = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("arcname")).FirstOrDefault();

            if (arcnameX != null)
            {
                arcname = arcnameX.Value;
            }

            XElement arcnameExpression = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("arcnameExpression")).FirstOrDefault();

            if (arcnameExpression != null)
            {
                arcname = EvaluateExpression(arcnameExpression.Value);
            }

            // linkname

            XElement linknameX = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("linkname")).FirstOrDefault();

            if (linknameX != null)
            {
                linkname = linknameX.Value;
            }

            XElement linknameExpression = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("linknameExpression")).FirstOrDefault();

            if (linknameExpression != null)
            {
                linkname = EvaluateExpression(linknameExpression.Value);
            }

            // axis

            XElement axisX = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("axis")).FirstOrDefault();

            if (axisX != null)
            {
                axis = axisX.Value;
            }

            // generations

            XElement generationsX = resource.ResourceXElement.Elements().Where(d => d.Name.LocalName.Equals("generations")).FirstOrDefault();

            if (generationsX != null)
            {
                generations = Int32.Parse(generationsX.Value);
            }
        }


        public override List<FactItem> EvaluateFilter(VariableSet variableSet, List<FactItem> itemsToFilter, DTS dts)
        {
            this.dts = dts;

            FactVariable factVariableFilter = GetFactVariableFromFilter(this.Label, variableSet);

            List<FactItem> itemListTemp = new List<FactItem>();

            List<string> concepts = new List<string>();

            networkRelationship = GetNetworkRelationship(dts);

            EvaluateSourceElement(variableSet, dts, concepts);            

            string result = "true";

            foreach (string sourceConcept in concepts)
            {
                bool isSourceConceptVerified = false;

                List<FactItem> temp = new List<FactItem>();

                List<Concept> conceptsRelationship = new List<Concept>();

                foreach (Arc arc in networkRelationship)
                {
                    Locator from = GetLocator(arc, arc.FromId);
                    Concept conceptFrom = dts.GetConceptByID(from.HrefResourceId);

                    Locator to = GetLocator(arc, arc.ToId);
                    Concept conceptTo = dts.GetConceptByID(to.HrefResourceId);

                    if (!string.IsNullOrEmpty(Test))
                    {
                        result = EvaluateXPathExpression(Test, arc.ArcElement.ToXmlNode());
                    }

                    isSourceConceptVerified = ApplyAxis(sourceConcept, conceptsRelationship, from, conceptFrom, conceptTo);

                    // xfi:root 

                    //VerifyXfiRoot(sourceConcept, conceptsRelationship, from, conceptFrom);

                    if (isSourceConceptVerified)
                    {
                        GetMatchItems(variableSet, itemsToFilter, factVariableFilter, ref itemListTemp, result, ref temp, conceptsRelationship);
                        break;
                    }
                }


            }   
         
            return itemListTemp; 
        }


        private void EvaluateSourceElement(VariableSet variableSet, DTS dts, List<string> concepts)
        {
            if (SourceElement.Equals("variable"))
            {
                FactVariable factVariable = GetFactVariable(Source, variableSet);

                List<FactItem> inputFacts = GetItems(dts.Instance.Facts);

                List<FactItem> itemsSource = variableSet.FilteringFacts(variableSet, factVariable, inputFacts);

                foreach (FactItem item in itemsSource)
                {
                    concepts.Add(item.Concept.Name);
                }
            }
            else // qname or qnameExpression
            {
                if (Source == "xfi:root")
                {
                    concepts.AddRange(GetRootConceptsName());
                }
                else
                {
                    concepts.Add(dts.GetConceptByName(Source.Substring(Source.IndexOf(':') + 1)).Name);
                }
            }
        }


        private List<string> GetRootConceptsName()
        {
            List<string> concepts = new List<string>();

            foreach (Arc arc in networkRelationship)
            {
                Locator from = GetLocator(arc, arc.FromId);
                Concept conceptFrom = dts.GetConceptByID(from.HrefResourceId);

                Locator to = GetLocator(arc, arc.ToId);
                Concept conceptTo = dts.GetConceptByID(to.HrefResourceId);

                if (from.InArcs.Count == 0)
                {
                    if (!concepts.Contains(conceptFrom.Name))
                    {
                        concepts.Add(conceptFrom.Name);
                    }
                }
            }

            return concepts;
        }


        public HashSet<Arc> GetNetworkRelationship(DTS dts)
        {
            foreach (Tuple<string, string, string, string> tuple in dts.NetworkRelationships.Keys)
            {
                if (tuple.Item2.Equals(linkrole) && (arcname != null ? tuple.Item3.Equals(arcname) : true) && tuple.Item4.Equals(arcrole))
                {
                    return dts.NetworkRelationships[tuple];

                }
            }
            return null;
        }


        internal bool ApplyAxis(string sourceConcept, List<Concept> conceptsRelationship, Locator from, Concept conceptFrom, Concept conceptTo)
        {
            switch (axis)
            {
                case "child-or-self":

                    if (sourceConcept.Equals(conceptFrom.Name))
                    {
                        conceptsRelationship.Add(conceptFrom);
                        conceptsRelationship.Add(conceptTo);

                        return true;
                    }

                    break;

                case "child":

                    if (sourceConcept.Equals(conceptFrom.Name))
                    {
                        conceptsRelationship.Add(conceptTo);

                        return true;
                    }

                    break;

                case "descendant-or-self":

                    if (sourceConcept.Equals(conceptFrom.Name))
                    {
                        if (generations > 0)
                        {
                            conceptsRelationship.AddRange(GetDescendants(from, generations));
                        }
                        else
                        {
                            conceptsRelationship.AddRange(GetDescendants(from));
                        }

                        conceptsRelationship.Add(conceptFrom);

                        return true;
                    }

                    break;

                case "descendant":

                    if (sourceConcept.Equals(conceptFrom.Name))
                    {
                        if (generations > 0)
                        {
                            conceptsRelationship.AddRange(GetDescendants(from, generations));
                        }
                        else
                        {
                            conceptsRelationship.AddRange(GetDescendants(from));
                        }

                        return true;
                    }

                    break;

                case "parent-or-self":

                    List<Concept> parentOrSelfConcepts = GetParents(from);
                    parentOrSelfConcepts.Add(conceptFrom);

                    if (sourceConcept.Equals(conceptFrom.Name))
                    {
                        conceptsRelationship.AddRange(parentOrSelfConcepts);

                        return true;
                    }

                    break;

                case "parent":

                    List<Concept> parentConcepts = GetParents(from);

                    if (sourceConcept.Equals(conceptFrom.Name))
                    {
                        conceptsRelationship.AddRange(parentConcepts);
                    }

                    break;

                case "ancestor-or-self":

                    if (sourceConcept.Equals(conceptFrom.Name))
                    {
                        List<Concept> ancestorOrSelfConcepts = GetAncestors(from);
                        ancestorOrSelfConcepts.Add(conceptFrom);

                        conceptsRelationship.AddRange(ancestorOrSelfConcepts);

                        return true;
                    }

                    break;

                case "ancestor":

                    if (sourceConcept.Equals(conceptFrom.Name))
                    {
                        List<Concept> ancestorConcepts = GetAncestors(from);

                        conceptsRelationship.AddRange(ancestorConcepts);

                        return true;
                    }

                    break;

                case "sibling":

                    if (sourceConcept.Equals(conceptFrom.Name))
                    {
                        List<Concept> siblingConcepts = GetSiblings(from, conceptFrom);

                        conceptsRelationship.AddRange(siblingConcepts);

                        return true;
                    }

                    break;

                case "sibling-or-self":

                    if (sourceConcept.Equals(conceptFrom.Name))
                    {
                        List<Concept> siblingOrSelfConcepts = GetSiblings(from, conceptFrom);
                        siblingOrSelfConcepts.Add(conceptFrom);

                        conceptsRelationship.AddRange(siblingOrSelfConcepts);

                        return true;
                    }

                    break;
                case "sibling-or-descendant":

                    if (sourceConcept.Equals(conceptFrom.Name))
                    {
                        List<Concept> siblingOrDescendantConcepts = GetSiblings(from, conceptFrom);
                        siblingOrDescendantConcepts.AddRange(GetDescendants(from));

                        conceptsRelationship.AddRange(siblingOrDescendantConcepts);

                        return true;
                    }

                    break;
            }

            return false;
        }


        private static void VerifyXfiRoot(string sourceConcept, List<Concept> conceptsRelationship, Locator from, Concept conceptFrom)
        {
            if (sourceConcept.Equals("xfi:root"))
            {
                if (from.InArcs.Count == 0 && conceptFrom.Equals(sourceConcept))
                {
                    conceptsRelationship.Add(conceptFrom);
                }
            }
        }


        private void GetMatchItems(VariableSet variableSet, List<FactItem> itemsToFilter, FactVariable factVariableFilter, ref List<FactItem> itemListTemp, string result, ref List<FactItem> temp, List<Concept> conceptsRelationship)
        {
            foreach (FactItem itemToFilter in itemsToFilter)
            {
                if (Complement ? !(result.Equals("true") && conceptsRelationship.Contains(itemToFilter.Concept)) :
                    result.Equals("true") && conceptsRelationship.Contains(itemToFilter.Concept))
                {
                    temp.Add(itemToFilter);
                }
            }

            if (factVariableFilter.BindAsSequence)
            {
                if (temp.Count > 0)
                {
                    variableSet.sequencesConceptRelationFilter.Add(temp);
                }
            }
            else
            {
                itemListTemp.AddRange(temp);
            }
        }


        private List<Concept> GetSiblings(Locator fromRoot, Concept fromConcept)
        {
            List<Concept> temp = new List<Concept>();

            if (fromRoot.InArcs.Count == 0)
            {
                List<Concept> rootConcepts = GetRootConcepts();

                if (rootConcepts.Contains(fromConcept))
                {
                    rootConcepts.Remove(fromConcept);
                    return rootConcepts;
                }
            }

            foreach (Arc arc in fromRoot.InArcs)
            {
                Locator from = GetLocator(arc, arc.FromId);

                foreach (Arc arcFrom in from.OutArcs)
                {
                    Locator to = GetLocator(arcFrom, arc.ToId);
                    Concept conceptTo = dts.GetConceptByID(to.HrefResourceId);

                    if (!conceptTo.Equals(fromConcept))
                    {
                        temp.Add(conceptTo);
                    }
                }
            }

            return temp;
        }


        private List<Concept> GetRootConcepts()
        {
            List<Concept> concepts = new List<Concept>();

            foreach (Arc arc in networkRelationship)
            {
                Locator from = GetLocator(arc, arc.FromId);
                Concept conceptFrom = dts.GetConceptByID(from.HrefResourceId);

                Locator to = GetLocator(arc, arc.ToId);
                Concept conceptTo = dts.GetConceptByID(to.HrefResourceId);

                if (from.InArcs.Count == 0)
                {
                    if (!concepts.Contains(conceptFrom))
                    {
                        concepts.Add(conceptFrom);
                    }
                }
            }

            return concepts;
        }


        private List<Concept> GetAncestors(Locator fromRoot)
        {
            List<Concept> temp = new List<Concept>();

            foreach (Arc arc in fromRoot.InArcs)
            {
                Locator from = GetLocator(arc, arc.FromId);
                Concept conceptFrom = dts.GetConceptByID(from.HrefResourceId);
                temp.Add(conceptFrom);

                temp.AddRange(this.GetAncestors(arc.FromLocator));
            }

            return temp;
        }


        private List<Concept> GetParents(Locator fromRoot)
        {
            List<Concept> temp = new List<Concept>();

            foreach (Arc arc in fromRoot.InArcs)
            {
                Locator from = GetLocator(arc, arc.FromId);
                Concept conceptFrom = dts.GetConceptByID(from.HrefResourceId);
                temp.Add(conceptFrom);
            }

            return temp;
        }


        private List<Concept> GetDescendants(Locator fromRoot, int level)
        {
            List<Concept> temp = new List<Concept>();

            if (level == 0)
            {
                return temp;
            }

            foreach (Arc arc in fromRoot.OutArcs)
            {
                Locator to = GetLocator(arc, arc.ToId);
                Concept conceptTo = dts.GetConceptByID(to.HrefResourceId);
                temp.Add(conceptTo);

                temp.AddRange(this.GetDescendants(arc.ToLocator, level - 1));
            }

            return temp;
        }


        private List<Concept> GetDescendants(Locator fromRoot)
        {
            List<Concept> temp = new List<Concept>();

            foreach (Arc arc in fromRoot.OutArcs)
            {
                Locator to = GetLocator(arc, arc.ToId);
                Concept conceptTo = dts.GetConceptByID(to.HrefResourceId);
                temp.Add(conceptTo);

                temp.AddRange(this.GetDescendants(arc.FromLocator));
            }

            return temp;
        }
    }
}
